/** @file f_zipper.cpp
	@brief Compressed folder handler */

#include "file/f_zipper.h"

using namespace G3;

//==============================================================================
// Constructor
//==============================================================================
G3::ZIPPER::ZIPPER () {
	CaseInsensitive = false;
	//LastBuffer = NULL;

	log = &MainLog;

	Decompressor = NULL;
	Compressor = NULL;
	FileSuccessfullyLoaded = false;
}

//==============================================================================
// Opens a Zip file
//
//  flags: w      Open for writing
//         r      Open for reading
//         s      SFX package mode
//  If none, the function just checkes for the Zip file existence
//==============================================================================
bool G3::ZIPPER::Open (const string &filename, const string &flags) {
	Assert (!filename.empty (), "ZIPPER::Open: Filename argument is a NULL pointer");
	Assert (!flags.empty (), "ZIPPER::Open: Flags argument is a NULL pointer");

    bool Append = false;
    bool Existence = true;
    bool Exist = FileExists (filename);
	bool Reading = false;

    if (strchr (flags.c_str (), 'r') != NULL) {
        Existence = false;
		Reading = true;
	}

    if (strchr (flags.c_str (), 'w') != NULL) {
        Existence = false;
    }

    if (strchr (flags.c_str (), 's') != NULL) {
        if (Exist)
            Append = true;
        else
            Append = false;

        Existence = false;
    }

	Decompressor = NULL;
	Compressor = NULL;

	if (Reading) {
		Decompressor = unzOpen (filename.c_str ());

		if (!Decompressor) {
			if (log)
				log->Report ("ZIPPER: Open: Failed to open file \"%s\" for decompression\n", filename.c_str ());
			return false;
		}
	} else {
		if (Append)
			Compressor = zipOpen (filename.c_str (), APPEND_STATUS_CREATEAFTER);
		else if (Exist)
			Compressor = zipOpen (filename.c_str (), APPEND_STATUS_ADDINZIP);
		else
			Compressor = zipOpen (filename.c_str (), APPEND_STATUS_CREATE);

		if (!Compressor) {
			if (log)
				log->Report ("ZIPPER: Open: Failed to open file \"%s\" for compression\n", filename.c_str ());
			return false;
		}
	}

    if (Existence)
          return Exist;

	FileSuccessfullyLoaded = true;

	return true;
}

//==============================================================================
// Closes the Zip file
//==============================================================================
bool G3::ZIPPER::Close () {
	int ret = 0;

	if (!FileSuccessfullyLoaded || (!Decompressor && !Compressor))
		return false;

	if (Decompressor) {
		ret = zipClose (Decompressor, NULL);
		Decompressor = NULL;

		if (ret != ZIP_OK) {
			if (log)
				log->Report ("ZIPPER: Failed to close the zip file (Error %d)..\n", ret);
			return false;
		}
	}

	if (Compressor) {
		ret = zipClose (Compressor, NULL);
		Compressor = NULL;

		if (ret != ZIP_OK) {
			if (log)
				log->Report ("ZIPPER: Failed to close the zip file (Error %d)..\n", ret);
			return false;
		}
	}

	FileSuccessfullyLoaded = false;
	return true;
}

//==============================================================================
// Inserts data as a file into the Zip file
//==============================================================================
bool G3::ZIPPER::Write (const string &filename, byte *data, ulong size, int compression) {
	Assert (!filename.empty (), "ZIPPER::Write: Filename argument is a NULL pointer");
	Assert (data, "ZIPPER::Write: Data argument is a NULL pointer");
	Assert (size, "ZIPPER::Write: Size argument is 0");

	zip_fileinfo finf;

	if (!Compressor || !FileSuccessfullyLoaded) {
		if (log)
			log->Report ("ERROR! ZIPPER::Write: The zip file hasn't been opened successfully..\n");
		return false;
	}

    // Get local time
    time_t date = time (NULL);
    struct tm *lt = localtime (&date);

	finf.tmz_date.tm_sec = lt->tm_sec;
	finf.tmz_date.tm_min = lt->tm_min;
	finf.tmz_date.tm_hour = lt->tm_hour;
	finf.tmz_date.tm_mday = lt->tm_mday;
	finf.tmz_date.tm_mon = lt->tm_mon;
	finf.tmz_date.tm_year = lt->tm_year;

	int ret = 0;

	if (compression == 0)
		ret = zipOpenNewFileInZip (Compressor, filename.c_str (), &finf, NULL, 0, NULL, 0, NULL, 0, 0);
	else
		ret = zipOpenNewFileInZip (Compressor, filename.c_str (), &finf, NULL, 0, NULL, 0, NULL, Z_DEFLATED, compression);

	if (ret != ZIP_OK) {
		if (log)
			log->Report ("ZIPPER: Write: Failed to create file \"%s\" (%d bytes) into the zip package\n", filename.c_str (), size);
		return false;
	}

	ret = zipWriteInFileInZip (Compressor, data, size);

	if (ret != ZIP_OK) {
		if (log)
			log->Report ("ZIPPER: Write: Failed to write into the file \"%s\" (%d bytes) in the zip package\n", filename.c_str (), size);
		return false;
	}

	ret = zipCloseFileInZip (Compressor);

	if (ret != ZIP_OK) {
		if (log)
			log->Report ("ZIPPER: Write: Failed to close the file \"%s\" (%d bytes) in the zip package\n", filename.c_str (), size);
		return false;
	}

    return true;
}

//==============================================================================
// Gets data from a file in the Zip file
//==============================================================================
byte *G3::ZIPPER::Read (const string &filename, byte *data, ulong *size) {
	Assert (!filename.empty (), "ZIPPER::Read: Filename argument is a NULL pointer");

	unz_file_info finf;
	int cases = 0;

	if (!Decompressor || !FileSuccessfullyLoaded) {
		if (log)
			log->Report ("ZIPPER: Read: The zip file hasn't been opened successfully..\n");
		return NULL;
	}

	if (CaseInsensitive)
		cases = 2;

	int ret = unzLocateFile (Decompressor, filename.c_str (), cases);

	if (ret != ZIP_OK) {
		if (log)
			log->Report ("ZIPPER: Read: Failed to locate the file \"%s\" in the Zip..\n", filename.c_str ());
		return NULL;
	}

	ret = unzGetCurrentFileInfo (Decompressor, &finf, NULL, 0, NULL, 0, NULL, 0);

	if (ret != ZIP_OK) {
		if (log)
			log->Report ("ZIPPER: Read: Failed to get information about the file \"%s\" in the Zip..\n", filename.c_str ());
		return NULL;
	}

	ret = unzOpenCurrentFile (Decompressor);
	if (ret != ZIP_OK) {
		if (log)
			log->Report ("ZIPPER: Read: Failed to open the file \"%s\" in the Zip..\n", filename.c_str ());
		return NULL;
	}

	try {
		if (!size)
			size = new ulong;

		*size = (ulong) finf.uncompressed_size;

		if (!data) {
			data = new byte [*size + 1];

			if (!data)
				throw 1;

			// Just in case for ASCII data
			data [*size] = 0;
		}
	} catch (...) {
		if (log)
			log->Report ("ZIPPER: Read: Not enough memory for storing %ld bytes from file \"%s\"..\n", *size, filename.c_str ());
    	return NULL;
	}

	//LastBuffer = data;

	ret = unzReadCurrentFile (Decompressor, data, *size);

	if (ret != ZIP_OK) {
		if (log)
	        log->Report ("ZIPPER: Read: Failed to read the file \"%s\" in the Zip..\n", filename.c_str ());
		return data;
	}

	ret = unzCloseCurrentFile (Decompressor);

	if (ret != ZIP_OK) {
		if (log)
	        log->Report ("ZIPPER: Read: Failed to close the file \"%s\" in the Zip..\n", filename.c_str ());
		return data;
	}

    return data;
}

//==============================================================================
// Inserts a file into the Zip file
//==============================================================================
bool G3::ZIPPER::InsertFile (const string &filename, const string &destination, int compression) {
	Assert (!filename.empty (), "ZIPPER::InsertFile: Filename argument is a NULL pointer");

    char *buf = NULL;
    struct stat s;
    long size = 0;
	bool err = false;

	if (!Compressor || !FileSuccessfullyLoaded) {
		if (log)
			log->Report ("ZIPPER: InsertFile: The zip file hasn't been opened successfully..\n");
		return false;
	}

	// Gets the attributes of the file
    stat (filename.c_str (), &s);

	try {
    	// Allocates some memory for the buffer
	    buf = new char [s.st_size];

		if (buf == NULL)
			throw 1;
	} catch (...) {
		if (log)
			log->Report ("ZIPPER: InsertFile: Not enough memory, at least %ld more bytes needed..\n", s.st_size);
		return false;
	}

    // Read in the file
    FILE *f = fopen (filename.c_str (), "rb");

	if (!f) {
		if (log)
	        log->Report ("ZIPPER: InsertFile: The file \"%s\" does not exist or is restricted..\n", filename.c_str ());

		try {
            delete [] buf;
		} catch (...) {
			if (log)
		        log->Report ("ZIPPER: InsertFile: Failed to free the allocated buffer..\n");
		}

        return false;
	}

    size = fread (buf, 1, s.st_size, f);

	if (size != s.st_size) {
        MainLog.Report ("ZIPPER: InsertFile: Warning only %ld bytes of total %ld were read..\n", size, s.st_size);
	}

    fclose (f);


	// And insert it into the Zip file with certain compression level
	if (destination.empty ())
    	err = Write (filename.c_str (), (byte *) buf, size, compression);
	else
		err = Write (destination.c_str (), (byte *) buf, size, compression);

	try {
        delete [] buf;
	} catch (...) {
		if (log)
	        log->Report ("ZIPPER: InsertFile: Failed to free the allocated buffer..\n");
	}

    return err;
}

//==============================================================================
// Gets a file from the Zip
//==============================================================================
bool G3::ZIPPER::GetFile (const string &src, const string &dest) {
	Assert (!src.empty (), "ZIPPER::GetFile: Source argument is a NULL pointer");
	Assert (!dest.empty (), "ZIPPER::GetFile: Destination argument is a NULL pointer");

    byte *buf = NULL, *buffer = NULL;
    ulong size = 0, size2 = 0;

	if (!Decompressor || !FileSuccessfullyLoaded) {
		if (log)
			log->Report ("ZIPPER: GetFIle: The zip file hasn't been opened successfully..\n");
		return false;
	}

    buffer = Read (src.c_str (), buf, &size);
    if (!buffer)
        return false;

    // And write it into a file
    FILE *f = fopen (dest.c_str (), "w");
    if (f) {
        size2 = fwrite (buf, 1, size, f);

        fclose (f);
    } else {
		if (log)
	        log->Report ("ZIPPER: GetFile: Failed to open file \"%s\" for writing..\n", dest.c_str ());
        return false;
    }

    if (size2 != size) {
		if (log)
	        log->Report ("ZIPPER: GetFile: Failed to completely copy the file \"%s\" to \"%s\"..\n", src.c_str (), dest.c_str ());
        return false;
    }

    return true;
}

//==============================================================================
// Checks if the zip file contains a file
//==============================================================================
bool G3::ZIPPER::ContainsFile (const string &filename) {
	Assert (!filename.empty (), "ZIPPER::ContainsFile: Filename argument is a NULL pointer");

	int cases = 0;

	if (!Decompressor || !FileSuccessfullyLoaded) {
		if (log)
			log->Report ("ZIPPER: ContainsFile: The zip file hasn't been opened successfully..\n");
		return false;
	}

	if (CaseInsensitive)
		cases = 2;

	return (unzLocateFile (Decompressor, filename.c_str (), cases) == ZIP_OK);
}

//==============================================================================
// Gets the uncompressed size of a file in the zip
//==============================================================================
long G3::ZIPPER::GetFileSize (const string &filename) {
	Assert (!filename.empty (), "ZIPPER::GetFileSize: Filename argument is a NULL pointer");

	unz_file_info finf;
	int cases = 0;

	if (!Decompressor || !FileSuccessfullyLoaded) {
		if (log)
			log->Report ("ZIPPER: GetFileSize: The zip file hasn't been opened successfully..\n");
		return 0;
	}

	if (CaseInsensitive)
		cases = 2;

	int ret = unzLocateFile (Decompressor, filename.c_str (), cases);

	if (ret != ZIP_OK) {
		if (log)
			log->Report ("ZIPPER: GetFileSize: Failed to locate the file \"%s\" in the Zip..\n", filename.c_str ());
		return 0;
	}

	ret = unzGetCurrentFileInfo (Decompressor, &finf, NULL, 0, NULL, 0, NULL, 0);

	if (ret != ZIP_OK) {
		if (log)
			log->Report ("ZIPPER: GetFileSize: Failed to get information about the file \"%s\" in the Zip..\n", filename.c_str ());
		return 0;
	}

	return finf.uncompressed_size;
}

